﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace peonwar
{
    public class GameServer : Network
    {
        public static int SyncOperation;

        private Game game;
        private TcpClient Client;
        private TcpListener Server;
        private NetworkStream Stream;
        public bool Connected;
        private Thread Listening;
        public override bool AllowReceive { get; set; }
        private bool AutoDisconnect;
        public int FunctionnalPort;
        public string HostIP;
        private int delay;
        public override int SyncDelay
        {
            get
            {
                return delay;
            }
            set
            {
                delay = value;
                if (delay < 1)
                {
                    SendUnitList();
                    delay = 60;
                }
            }
        }

        public GameServer(int port, Game g)
        {
            this.game = g;
            this.AutoDisconnect = false;
            this.HostIP = GetIP();
            this.FunctionnalPort = port;

            Server = new TcpListener(IPAddress.Any, FunctionnalPort);
            Listening = new Thread(new ThreadStart(WaitForConnection));
            Listening.IsBackground = true;
            Listening.Start();
        }

        public void Initialize()
        {
            Stream = Client.GetStream();
            Server.Stop();

            AllowReceive = true;
            Connected = true;
            delay = 60;

            CommandProcessor.Start(game, this);
        }

        public void WaitForConnection()
        {
            try
            {
                Server.Start(1);
                DevConsole.AddOutput("--- SERVER STARTED ---");
                DevConsole.AddOutput("Adress: " + HostIP + ":" + FunctionnalPort);
                DevConsole.AddOutput("Waiting for connection request...");
                Client = Server.AcceptTcpClient();
                DevConsole.AddOutput("Request accepted!");
            }
            catch
            {
                if (AutoDisconnect)
                {
                    DevConsole.AddOutput("Server has been shut down by the Host");
                    AutoDisconnect = false;
                }
                else
                {
                    DevConsole.AddOutput("Unable to receive connection.");
                    DevConsole.AddOutput("The port " + FunctionnalPort + " could be already used.");
                    Game.gamestate = Gamestates.ChooseVersus;
                }
                return;
            }
            Initialize();
        }

        public override void Disconnect(bool gotodisconnectmenu, string message, string detail)
        {
            AllowReceive = false;
            Dispose();

            game.EndGame();
            game.Join_IsConnecting = false;
            game.Join_Failed = false;
            game.CurrentSession = Session.None;
            MediaPlayer.Play(GameSound.MenuMusic);

            DevConsole.AddOutput("Disconnected - Reason :");
            DevConsole.AddOutput(message);

            if (gotodisconnectmenu)
            {
                game.Disconnected_Message = message;
                game.Disconnected_Detail = detail;
                Game.gamestate = Gamestates.Disconnected;
            }
            else
            {
                Game.gamestate = Gamestates.ChooseVersus;
            }
        }

        #region IO
        public override void Send(RequestType request, object data)
        {
            try
            {
                byte[] CommandByte = new byte[] { (byte)request };
                Stream.Write(CommandByte, 0, CommandByte.Length);

                byte[] Data = CommandProcessor.ObjectToByteArray(data);
                byte[] Size = BitConverter.GetBytes(Data.Length);
                Stream.Write(Size, 0, Size.Length);
                Stream.Write(Data, 0, Data.Length);
            }
            catch (Exception e)
            {
                Disconnect(true, GameData.GameString[218], e.Message);
            }
        }

        public override Tuple<RequestType, object> Receive()
        {
            byte[] IncomingCommandByte = new byte[1];
            byte IncomingCommand;
            Stream.Read(IncomingCommandByte, 0, IncomingCommandByte.Length);
            IncomingCommand = IncomingCommandByte[0];
            RequestType Request = (RequestType)IncomingCommand;

            byte[] IncomingSizeByte = new byte[4];
            int IncomingSize;
            Stream.Read(IncomingSizeByte, 0, IncomingSizeByte.Length);
            IncomingSize = BitConverter.ToInt32(IncomingSizeByte, 0);

            byte[] IncomingData = new byte[IncomingSize];
            CommandProcessor.ReadWholeArray(Stream, IncomingData);
            object dataObject;
            dataObject = CommandProcessor.ByteArrayToObject(IncomingData);

            return new Tuple<RequestType, object>(Request, dataObject);
        }

        public void SendUnitList()
        {
            SynchroInfos infos = new SynchroInfos();

            infos.GetBlueUnits(Game.blue.PeonList);
            infos.GetRedUnits(Game.red.PeonList);

            infos.GetBase(Game.blue, Game.red);
            infos.GameElapsedTime = game.GameChrono.ElapsedTime;

            Send(RequestType.Game_Synchronize, infos);

            GameServer.SyncOperation++;
        }
        #endregion

        #region Tools
        public void Dispose()
        {
            AutoDisconnect = true;
            Server.Stop();
            if (Stream != null)
            {
                Stream.Close();
                Stream.Dispose();
            }
            if (Client != null)
                Client.Close();
            Connected = false;
        }

        private string GetIP()
        {
            IPHostEntry host;
            string localIP = "?";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP = ip.ToString();
                    break;
                }
            }
            return localIP;
        }
        #endregion

    }
}
